Timer
Introduction
A timer module is probably one of the most basic pieces of hardware. But even for a timer, there are some interesting things that you can do with SpinalHDL. This example will define a simple timer component which integrates a bus bridging utile.
Timer
So let’s start with the Timer component.
Specification
The Timer component will have a single construction parameter:
| Parameter Name | Type | Description | 
|---|---|---|
| width | Int | Specify the bit width of the timer counter | 
And also some inputs/outputs:
| IO Name | Direction | Type | Description | 
|---|---|---|---|
| tick | in | Bool | When  | 
| clear | in | Bool | When  | 
| limit | in | UInt(width bits) | When the timer value is equal to  | 
| full | out | Bool | 
 | 
| value | out | UInt(width bits) | Wire out the timer counter value. | 
Implementation
case class Timer(width : Int) extends Component{
  val io = new Bundle{
    val tick      = in Bool()
    val clear     = in Bool()
    val limit     = in UInt(width bits)
    val full  = out Bool()
    val value     = out UInt(width bits)
  }
  val counter = Reg(UInt(width bits))
  when(io.tick && !io.full){
    counter := counter + 1
  }
  when(io.clear){
    counter := 0
  }
  io.full := counter === io.limit && io.tick
  io.value := counter
}
Bridging function
Now we can start with the main purpose of this example: defining a bus bridging function. To do that we will use two techniques:
- Using the - BusSlaveFactorytool documented here
- Defining a function inside the - Timercomponent which can be called from the parent component to drive the- Timer‘s IO in an abstract way.
Specification
This bridging function will take the following parameters:
| Parameter Name | Type | Description | 
|---|---|---|
| busCtrl | BusSlaveFactory | The  | 
| baseAddress | BigInt | The base address where the bridging logic should be mapped. | 
| ticks | Seq[Bool] | A list of Bool sources that can be used as a tick signal. | 
| clears | Seq[Bool] | A list of Bool sources that can be used as a clear signal. | 
The register mapping assumes that the bus system is 32 bits wide:
| Name | Access | Width | Address offset | Bit offset | Description | 
|---|---|---|---|---|---|
| ticksEnable | RW | len(ticks) | 0 | 0 | Each  | 
| clearsEnable | RW | len(clears) | 0 | 16 | Each  | 
| limit | RW | width | 4 | 0 | Access the limit value of the timer component. When this register is written to, the timer is cleared. | 
| value | R | width | 8 | 0 | Access the value of the timer. | 
| clear | W | 8 | When this register is written to, it clears the timer. | 
Implementation
Let’s add this bridging function inside the Timer component.
case class Timer(width : Int) extends Component{
  val io = new Bundle{
    val tick      = in Bool()
    val clear     = in Bool()
    val limit     = in UInt(width bits)
    val full  = out Bool()
    val value     = out UInt(width bits)
  }
  // Logic previously defined
  // ....
  // The function prototype uses Scala currying funcName(arg1,arg2)(arg3,arg3)
  // which allow to call the function with a nice syntax later
  // This function also returns an area, which allows to keep names of inner signals in the generated VHDL/Verilog.
  def driveFrom(busCtrl : BusSlaveFactory,baseAddress : BigInt)(ticks : Seq[Bool],clears : Seq[Bool]) = new Area {
    //Address 0 => clear/tick masks + bus
    val ticksEnable  = busCtrl.createReadWrite(Bits(ticks.length bits),baseAddress + 0,0) init(0)
    val clearsEnable = busCtrl.createReadWrite(Bits(clears.length bits),baseAddress + 0,16) init(0)
    val busClearing  = False
    io.clear := (clearsEnable & clears.asBits).orR | busClearing
    io.tick  := (ticksEnable  & ticks.asBits ).orR
    //Address 4 => read/write limit (+ auto clear)
    busCtrl.driveAndRead(io.limit,baseAddress + 4)
    busClearing setWhen(busCtrl.isWriting(baseAddress + 4))
    //Address 8 => read timer value / write => clear timer value
    busCtrl.read(io.value,baseAddress + 8)
    busClearing setWhen(busCtrl.isWriting(baseAddress + 8))
  }
}
Usage
Here is some demonstration code which is very close to the one used in the Pinsec SoC timer module. Basically it instantiates following elements:
- One 16 bit prescaler 
- One 32 bit timer 
- Three 16 bit timers 
Then by using an Apb3SlaveFactory and functions defined inside the Timers, it creates bridging logic between the APB3 bus and all instantiated components.
val io = new Bundle{
  val apb = Apb3(ApbConfig(addressWidth = 8, dataWidth = 32))
  val interrupt = in Bool()
  val external = new Bundle{
    val tick  = Bool()
    val clear = Bool()
  }
}
//Prescaler is very similar to the timer, it mainly integrates a piece of auto reload logic.
val prescaler = Prescaler(width = 16)
val timerA = Timer(width = 32)
val timerB,timerC,timerD = Timer(width = 16)
val busCtrl = Apb3SlaveFactory(io.apb)
val prescalerBridge = prescaler.driveFrom(busCtrl,0x00)
val timerABridge = timerA.driveFrom(busCtrl,0x40)(
  // The first element is True, which allows you to have a mode where the timer is always counting up.
  ticks  = List(True, prescaler.io.overflow),
  // By looping the timer full to the clears, it allows you to create an autoreload mode.
  clears = List(timerA.io.full)
)
val timerBBridge = timerB.driveFrom(busCtrl,0x50)(
  //The external.tick could allow to create an impulsion counter mode
  ticks  = List(True, prescaler.io.overflow, io.external.tick),
  //external.clear could allow to create an timeout mode.
  clears = List(timerB.io.full, io.external.clear)
)
val timerCBridge = timerC.driveFrom(busCtrl,0x60)(
  ticks  = List(True, prescaler.io.overflow, io.external.tick),
  clears = List(timerC.io.full, io.external.clear)
)
val timerDBridge = timerD.driveFrom(busCtrl,0x70)(
  ticks  = List(True, prescaler.io.overflow, io.external.tick),
  clears = List(timerD.io.full, io.external.clear)
)
val interruptCtrl = InterruptCtrl(4)
val interruptCtrlBridge = interruptCtrl.driveFrom(busCtrl,0x10)
interruptCtrl.io.inputs(0) := timerA.io.full
interruptCtrl.io.inputs(1) := timerB.io.full
interruptCtrl.io.inputs(2) := timerC.io.full
interruptCtrl.io.inputs(3) := timerD.io.full
io.interrupt := interruptCtrl.io.pendings.orR